package org.myspringframework.mvc.processor.impl;

import lombok.extern.slf4j.Slf4j;
import org.myspringframework.core.BeanContainer;
import org.myspringframework.core.util.CollectionUtils;
import org.myspringframework.core.util.ConvertUtil;
import org.myspringframework.mvc.RequestProcessorChain;
import org.myspringframework.mvc.annotation.RequestMapping;
import org.myspringframework.mvc.annotation.RequestParam;
import org.myspringframework.mvc.annotation.ResponseBody;
import org.myspringframework.mvc.processor.RequestProcessor;
import org.myspringframework.mvc.render.JsonResultRender;
import org.myspringframework.mvc.render.ResourceNotFoundResultRender;
import org.myspringframework.mvc.render.ResultRender;
import org.myspringframework.mvc.render.ViewResultRender;
import org.myspringframework.mvc.type.ControllerMethod;
import org.myspringframework.mvc.type.RequestPathInfo;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * org.myspringframework.mvc.processor.impl <br>
 * Controller请求处理器
 *
 * @author jaymin
 * @since 2021/5/22
 */
@Slf4j
public class ControllerRequestProcessor implements RequestProcessor {
    /**
     * IoC容器
     */
    private BeanContainer beanFactory;
    /**
     * 请求和controller方法的映射集合
     */
    private Map<RequestPathInfo, ControllerMethod> pathControllerMethodMap = new ConcurrentHashMap<>();

    /**
     * 依靠容器的能力，建立起请求路径、请求方法与Controller方法实例的映射
     */
    public ControllerRequestProcessor() {
        this.beanFactory = BeanContainer.getInstance();
        Set<Class<?>> requestMappingSet = beanFactory.getClassesByAnnotation(RequestMapping.class);
        initPathControllerMethodMap(requestMappingSet);

    }

    /**
     * 1. 遍历所有被@RequestMapping标记的类,获取类上面该注解的属性值作为一级路径. <br>
     * 2. 遍历类里所有被@RequestMapping标记的方法，获取方法上面该注解的属性值，作为二级路径. <br>
     * 3. 解析方法里被@RequestParam标记的参数,获取该注解的属性值，作为参数名<br>
     * 获取被标记的参数的数据类型，建立参数名和参数类型的映射. <br>
     * 4. 将获取到的信息封装成RequestPathInfo实例和ControllerMethod实例，放置到映射表中. <br>
     *
     * @param requestMappingSet 被@RequestMapping标记的类
     */
    private void initPathControllerMethodMap(Set<Class<?>> requestMappingSet) {
        if (CollectionUtils.isEmpty(requestMappingSet)) {
            return;
        }
        requestMappingSet.forEach(requestMappingClass -> {
            RequestMapping requestMapping = requestMappingClass.getAnnotation(RequestMapping.class);
            String basePath = requestMapping.value();
            if (!basePath.startsWith("/")) {
                basePath = "/" + basePath;
            }
            Method[] methods = requestMappingClass.getDeclaredMethods();
            if (CollectionUtils.isEmpty(methods)) {
                return;
            }
            for (Method method : methods) {
                if (method.isAnnotationPresent(RequestMapping.class)) {
                    buildRequestMethodMap(requestMappingClass, basePath, method);
                }
            }
        });
    }

    private void buildRequestMethodMap(Class<?> requestMappingClass, String basePath, Method method) {
        RequestMapping methodRequest = method.getAnnotation(RequestMapping.class);
        String methodPath = methodRequest.value();
        if (!methodPath.startsWith("/")) {
            methodPath = "/" + methodPath;
        }
        String url = basePath + methodPath;
        Map<String, Class<?>> methodParams = new HashMap<>();
        Parameter[] parameters = method.getParameters();
        if (CollectionUtils.isEmpty(parameters)) {
            addControllerMethod(requestMappingClass, method, methodRequest, url, methodParams);
            return;
        }
        for (Parameter parameter : parameters) {
            if (ServletRequest.class.isAssignableFrom(parameter.getType()) || ServletResponse.class.isAssignableFrom(parameter.getType())) {
                methodParams.put(parameter.getName(), parameter.getType());
                continue;
            }
            RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
            if (Objects.isNull(requestParam)) {
                throw new RuntimeException("The parameter must have @RequestParam");
            }
            methodParams.put(requestParam.value(), parameter.getType());
        }
        addControllerMethod(requestMappingClass, method, methodRequest, url, methodParams);
    }

    private void addControllerMethod(Class<?> requestMappingClass, Method method, RequestMapping methodRequest, String url, Map<String, Class<?>> methodParams) {
        String httpMethod = String.valueOf(methodRequest.method());
        RequestPathInfo requestPathInfo = new RequestPathInfo(httpMethod, url);
        if (this.pathControllerMethodMap.containsKey(requestPathInfo)) {
            log.warn("duplicate url:{},registration,current class {} method {}" +
                            " will override the former one", requestPathInfo.getHttpPath(),
                    requestMappingClass.getName(), method.getName());
        }
        ControllerMethod controllerMethod = new ControllerMethod(requestMappingClass, method, methodParams);
        this.pathControllerMethodMap.put(requestPathInfo, controllerMethod);
    }

    /**
     * 1. 解析httpServletRequest的请求方法，请求路径，获取对应的ControllerMethod实例. <br>
     * 2. 解析请求参数，并传递给获取到的ControllerMethod实例去执行. <br>
     * 3. 根据处理的结果，选择对应的render进行渲染. <br>
     *
     * @param requestProcessorChain
     * @return
     * @throws Exception
     */
    @Override
    public boolean proceed(RequestProcessorChain requestProcessorChain) throws Exception {
        String requestPath = requestProcessorChain.getRequestPath();
        String requestMethod = requestProcessorChain.getRequestMethod();
        ControllerMethod controllerMethod = this.pathControllerMethodMap.get(new RequestPathInfo(requestMethod, requestPath));
        if (Objects.isNull(controllerMethod)) {
            requestProcessorChain.setResultRender(new ResourceNotFoundResultRender(requestMethod, requestPath));
            return false;
        }
        Object result = invokeControllerMethod(controllerMethod, requestProcessorChain.getHttpServletRequest());
        setResultRender(result, controllerMethod, requestProcessorChain);
        return true;
    }

    /**
     * 根据不同的情况设置不同的渲染器
     *
     * @param result                返回结果值
     * @param controllerMethod      执行方法
     * @param requestProcessorChain 拦截链
     */
    private void setResultRender(Object result, ControllerMethod controllerMethod, RequestProcessorChain requestProcessorChain) {
        ResultRender resultRender;
        boolean hasResponseBody = controllerMethod.getInvokeMethod().isAnnotationPresent(ResponseBody.class);
        if (hasResponseBody) {
            resultRender = new JsonResultRender(result);
        } else {
            resultRender = new ViewResultRender(result);
        }
        requestProcessorChain.setResultRender(resultRender);
    }

    /**
     * 1. 从请求里获取GET获取POST的参数名及其对应的值 <br>
     * 2. 根据获取到的请求参数名及其对应的值，以及controllerMethod里面的参数和类型的映射关系，去实例化方法. <br>
     * 3. 执行controller里面对应的方法并返回结果. <br>
     *
     * @param controllerMethod
     * @param httpServletRequest
     * @return
     */
    private Object invokeControllerMethod(ControllerMethod controllerMethod, HttpServletRequest httpServletRequest) {
        Map<String, String> requestParamMap = new HashMap<>();
        // only support GET or POST
        Map<String, String[]> parameterMap = httpServletRequest.getParameterMap();
        parameterMap.forEach((parameterName, parameterValues) -> {
            if (CollectionUtils.isEmpty(parameterValues)) {
                return;
            }
            requestParamMap.put(parameterName, parameterValues[0]);
        });
        List<Object> methodParams = new ArrayList<>();
        Map<String, Class<?>> methodParameterMap = controllerMethod.getMethodParameters();
        for (String paramName : methodParameterMap.keySet()) {
            Class<?> parameterType = methodParameterMap.get(paramName);
            String requestValue = requestParamMap.get(paramName);
            Object value;
            // 如果请求参数为空,String|char|int|short|byte|double|long|float|boolean赋予默认值
            if (Objects.isNull(requestValue)) {
                value = ConvertUtil.primitiveNull(parameterType);
            } else {
                value = ConvertUtil.convert(parameterType, requestValue);
            }
            methodParams.add(value);
        }
        Object controller = beanFactory.getBean(controllerMethod.getControllerClass());
        Method invokeMethod = controllerMethod.getInvokeMethod();
        invokeMethod.setAccessible(true);
        Object result;
        try {
            if (CollectionUtils.isEmpty(methodParams)) {
                result = invokeMethod.invoke(controller);
            } else {
                result = invokeMethod.invoke(controller, methodParams.toArray());
            }
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e.getTargetException());
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
        return result;
    }
}
